#include "sdk.h"
#include "directorymonitor.h"
#include <vector>
#include "se_globals.h"
#include <iostream>
#include <map>
#include <fam.h>
#include <sys/time.h>

DEFINE_EVENT_TYPE( wxEVT_MONITOR_NOTIFY )
DEFINE_EVENT_TYPE( wxEVT_MONITOR_NOTIFY2 )

wxDirectoryMonitorEvent::wxDirectoryMonitorEvent( const wxString &mon_dir, int event_type, const wxString &uri ): wxNotifyEvent( wxEVT_MONITOR_NOTIFY ) {
  m_mon_dir = mon_dir;
  m_event_type = event_type;
  m_info_uri = wxString( uri.c_str() );
}

wxDirectoryMonitorEvent::wxDirectoryMonitorEvent( const wxDirectoryMonitorEvent& c ) : wxNotifyEvent( c ) {
  m_mon_dir = wxString( c.m_mon_dir.c_str() );
  m_event_type = c.m_event_type;
  m_info_uri = wxString( c.m_info_uri.c_str() );
}

typedef std::map<FAMRequest, wxString> MonMap;

struct MonDescriptors {
  MonDescriptors( int event_pipe ) {
    FAMOpen( &fc );
    read_pipe = event_pipe;
  }
  ~MonDescriptors() {
    FAMClose( &fc );
  }
  int pipe_set() {
    return FD_ISSET( read_pipe, &readset );
  }
  int fam_set() {
    return FD_ISSET( famfd(), &readset );
  }
  int nfds() {
    return famfd() > read_pipe ? famfd() + 1 : read_pipe + 1;
  }
  int famfd() {
    return FAMCONNECTION_GETFD( &fc );
  }
  FAMConnection *fam() {
    return &fc;
  }
  int do_select() {
    int result;
    do {
      FD_ZERO( &readset );
      FD_SET( famfd(), &readset );
      FD_SET( read_pipe, &readset );
      result = select( nfds(), &readset, NULL, NULL, NULL );
    } while( false );
    if( result > 0 ) {
      if( FD_ISSET( famfd(), &readset ) ) {
        return 1;
      }
      if( FD_ISSET( read_pipe, &readset ) ) {
        return 2;
      }
    }
    return -1;
  }
  FAMConnection fc;
  int read_pipe;
  fd_set readset;
};

class DirMonitorThread : public wxThread {
  public:
    DirMonitorThread( wxEvtHandler *parent, wxArrayString pathnames, bool singleshot, bool subtree, int notifyfilter, int waittime_ms )
      : wxThread( wxTHREAD_JOINABLE ) {
      m_active = false;
      m_parent = parent;
      m_waittime = waittime_ms;
      m_subtree = subtree;
      m_singleshot = singleshot;
      for( unsigned int i = 0; i < pathnames.GetCount(); i++ ) {
        m_pathnames.Add( pathnames[i].c_str() );
      }
      m_notifyfilter = notifyfilter;
      int pipehandles[2];
      pipe( pipehandles );
      m_msg_rcv = pipehandles[0];
      m_msg_send = pipehandles[1];
      return;
    }
    void UpdatePathsThread( MonDescriptors &fd ) {
      std::vector<FAMRequest *> new_h( m_update_paths.GetCount(), NULL );
      for( size_t i = 0; i < m_pathnames.GetCount(); i++ ) {
        int index = m_update_paths.Index( m_pathnames[i] );
        if( index == wxNOT_FOUND ) {
          if( m_h[i] ) {
            FAMCancelMonitor( fd.fam(), m_h[i] );
            delete m_h[i];
          }
        }
      }
      for( size_t i = 0; i < m_update_paths.GetCount(); i++ ) {
        int index = m_pathnames.Index( m_update_paths[i] );
        if( index != wxNOT_FOUND ) {
          new_h[i] = m_h[index];
        } else {
          FAMRequest *fr = new FAMRequest;
          if( FAMMonitorDirectory( fd.fam(), m_update_paths[i].mb_str( wxConvLocal ), fr, new wxString( m_update_paths[i].c_str() ) ) >= 0 ) {
            new_h[i] = fr;
            m_active_count++;
          } else
          { delete fr; }
        }
      }
      m_h = new_h;
      m_pathnames = m_update_paths;
    }
    void *Entry() {
      MonDescriptors fd( m_msg_rcv );
      m_interrupt_mutex.Lock();
      m_thread_notify = false;
      m_active = true;
      m_interrupt_mutex.Unlock();
      UpdatePathsThread( fd );
      bool quit = false;
      m_active_count = 0;
      while( !quit ) {
        const int result1 = fd.do_select();
        if( result1 < 0 ) {
          break;
        }
        if( fd.fam_set() ) {
          while( FAMPending( fd.fam() ) ) {
            FAMEvent fe;
            int result = FAMNextEvent( fd.fam(), &fe );
            if( result > 0 ) {
              wxString target_path( fe.filename, wxConvLocal );
              int action = 0;
              switch( fe.code ) {
              case FAMChanged:
                action = MONITOR_FILE_CHANGED;
                break;
              case FAMDeleted:
              case FAMMoved:
                action = MONITOR_FILE_DELETED;
                break;
              case FAMCreated:
                action = MONITOR_FILE_CREATED;
                break;
              case FAMEndExist:
              case FAMExists:
                break;
              case FAMAcknowledge:
                if( fe.userdata )
                { delete( wxString* )fe.userdata; }
                m_active_count--;
                break;
              case FAMStartExecuting:
              case FAMStopExecuting:
              default:
                break;
              }
              if( action & m_notifyfilter ) {
                wxDirectoryMonitorEvent e( ( ( wxString * )( fe.userdata ) )->c_str(), action, target_path.c_str() );
                m_parent->AddPendingEvent( e );
              }
            }
          }
        }
        if( fd.pipe_set() ) {
          char c = 0;
          read( m_msg_rcv, &c, 1 );
          switch( c ) {
          case 'm':
            UpdatePathsThread( fd );
            break;
          case 'q':
            quit = true;
            m_interrupt_mutex.Lock();
            m_active = false;
            m_update_paths.Empty();
            m_interrupt_mutex.Unlock();
            UpdatePathsThread( fd );
            break;
          default:
            break;
          }
        }
      }
      return NULL;
    }
    virtual ~DirMonitorThread() {
      m_interrupt_mutex.Lock();
      m_active = false;
      char m = 'q';
      write( m_msg_send, &m, 1 );
      m_interrupt_mutex.Unlock();
      if( IsRunning() ) {
        Wait();
      }
      close( m_msg_rcv );
      close( m_msg_send );
    }
    void UpdatePaths( const wxArrayString &paths ) {
      m_interrupt_mutex.Lock();
      if( !m_active ) {
        m_interrupt_mutex.Unlock();
        return;
      }
      m_update_paths.Empty();
      for( unsigned int i = 0; i < paths.GetCount(); i++ ) {
        m_update_paths.Add( paths[i].c_str() );
      }
      char m = 'm';
      write( m_msg_send, &m, 1 );
      m_interrupt_mutex.Unlock();
    }
    int m_active_count;
    int m_msg_rcv;
    int m_msg_send;
    bool m_thread_notify;
    bool m_active;
    wxMutex m_interrupt_mutex;
    int m_waittime;
    bool m_subtree;
    bool m_singleshot;
    wxArrayString m_pathnames, m_update_paths;
    int m_notifyfilter;
    MonMap m_monmap;
    std::vector<FAMRequest *> m_h;
    wxEvtHandler *m_parent;
};

BEGIN_EVENT_TABLE( wxDirectoryMonitor, wxEvtHandler )
  EVT_MONITOR_NOTIFY( 0, wxDirectoryMonitor::OnMonitorEvent )
END_EVENT_TABLE()

void wxDirectoryMonitor::OnMonitorEvent( wxDirectoryMonitorEvent &e ) {
  if( m_parent ) {
    m_parent->AddPendingEvent( e );
  }
}

wxDirectoryMonitor::wxDirectoryMonitor( wxEvtHandler *parent, const wxArrayString &uri, int eventfilter ) {
  m_parent = parent;
  m_uri = uri;
  m_eventfilter = eventfilter;
}

bool wxDirectoryMonitor::Start() {
  m_monitorthread = new DirMonitorThread( this, m_uri, false, false, m_eventfilter, 100 );
  m_monitorthread->Create();
  m_monitorthread->Run();
  return true;
}

void wxDirectoryMonitor::ChangePaths( const wxArrayString &uri ) {
  m_uri = uri;
  m_monitorthread->UpdatePaths( uri );
}

wxDirectoryMonitor::~wxDirectoryMonitor() {
  delete m_monitorthread;
}
